import GameFrameworkModule from "../Base/GameFrameworkModule";
import {ISoundManager} from "./ISoundManager";
import {PlaySoundDependencyAssetEventArgs} from "./PlaySoundDependencyAssetEventArgs";
import {PlaySoundFailureEventArgs} from "./PlaySoundFailureEventArgs";
import {PlaySoundSuccessEventArgs} from "./PlaySoundSuccessEventArgs";
import {PlaySoundUpdateEventArgs} from "./PlaySoundUpdateEventArgs";
import {ISoundAgentHelper} from "./ISoundAgentHelper";
import {ISoundGroupHelper} from "./ISoundGroupHelper";
import {ISoundGroup} from "./ISoundGroup";
import {PlaySoundParams} from "./PlaySoundParams";
import {IResourceManager} from "../Resources/IResourceManager";
import {ISoundHelper} from "./ISoundHelper";
import {SoundGroup} from "./SoundGroup";
import {Sound_Constant} from "./Constant";
import {PlaySoundErrorCode} from "./PlaySoundErrorCode";
import {ReferencePool} from "../Base/ReferencePool/ReferencePool";
import {PlaySoundInfo} from "./PlaySoundInfo";

export class SoundManager implements GameFrameworkModule, ISoundManager {

    set PlaySoundFailure(value: (sender, e: PlaySoundFailureEventArgs) => void) {
        this._playSoundFailureEventHandler = value;
    }

    set PlaySoundUpdate(value: (sender, e: PlaySoundUpdateEventArgs) => void) {
        this._playSoundUpdateEventHandler = value;
    }

    set PlaySoundDependencyAsset(value: (sender, e: PlaySoundDependencyAssetEventArgs) => void) {
        this._playSoundDependencyAssetEventHandler = value;
    }

    set PlaySoundSuccess(value: PlaySoundSuccessEventArgs) {
        this._playSoundSuccessEventHandler = value;
    }

    get SoundGroupCount(): number {
        return this._soundGroups.size;
    }

    private readonly _soundGroups: Map<string, SoundGroup>;
    private readonly _soundsBeingLoaded: number[];
    private readonly _soundsToReleaseOnLoad: Set<number>;
    private readonly _loadAssetCallbacks: (successevent, failevent, updateevent, AssetDependencyevent) => void;
    private _resourceManager: IResourceManager;
    private _soundHelper: ISoundHelper;
    private _serial: number;
    private _playSoundSuccessEventHandler: PlaySoundSuccessEventArgs;
    private _playSoundFailureEventHandler: (sender, e: PlaySoundFailureEventArgs) => void;
    private _playSoundUpdateEventHandler: (sender, e: PlaySoundUpdateEventArgs) => void;
    private _playSoundDependencyAssetEventHandler: (sender, e: PlaySoundDependencyAssetEventArgs) => void;

    constructor() {
        this._soundGroups = new Map<string, SoundGroup>();
        this._soundsBeingLoaded = [];
        this._soundsToReleaseOnLoad = new Set<number>();
        //m_LoadAssetCallbacks = new LoadAssetCallbacks(LoadAssetSuccessCallback, LoadAssetFailureCallback, LoadAssetUpdateCallback, LoadAssetDependencyAssetCallback);
        this._resourceManager = null;
        this._soundHelper = null;
        this._serial = 0;
        this._playSoundSuccessEventHandler = null;
        this._playSoundFailureEventHandler = null;
        this._playSoundUpdateEventHandler = null;
        this._playSoundDependencyAssetEventHandler = null;
    }

    AddSoundAgentHelper(soundGroupName: string, soundAgentHelper: ISoundAgentHelper): void {
        if (this._soundHelper == null)
        {
            throw new Error("You must set sound helper first.");
        }

        let soundGroup:SoundGroup = this.GetSoundGroup(soundGroupName) as SoundGroup;
        if (soundGroup == null)
        {
            throw new Error("Sound group {"+soundGroupName+"} is not exist.");
        }

        soundGroup.AddSoundAgentHelper(this._soundHelper, soundAgentHelper);
    }

    AddSoundGroup(soundGroupName: string, soundGroupAvoidBeingReplacedBySamePriority: boolean, soundGroupHelper?: ISoundGroupHelper,soundGroupMute?: boolean, soundGroupVolume?: number): boolean {
        if (soundGroupName == "" || soundGroupName == undefined) {
            throw new Error("Sound group name is invalid.");
        }

        if (soundGroupHelper == null) {
            throw new Error("Sound group helper is invalid.");
        }

        if (this.HasSoundGroup(soundGroupName)) {
            return false;
        }

        let soundGroup: SoundGroup = new SoundGroup(soundGroupName, soundGroupHelper);
        soundGroup.AvoidBeingReplacedBySamePriority = soundGroupAvoidBeingReplacedBySamePriority;
        soundGroup.Mute = soundGroupMute?soundGroupMute:Sound_Constant.DefaultMute;
        soundGroup.Volume = soundGroupVolume?soundGroupVolume:Sound_Constant.DefaultVolume;
        this._soundGroups.set(soundGroupName, soundGroup);
        return true;
    }

    GetAllLoadingSoundSerialIds(): number[] {
        return this._soundsBeingLoaded;
    }

    GetAllSoundGroups(): ISoundGroup[] {
        let result: ISoundGroup[] = [];
        this._soundGroups.forEach((value, key, map) => {
            result.push(value);
        })
        return result;
    }

    GetSoundGroup(soundGroupName: string): ISoundGroup {
        if (soundGroupName == "" || soundGroupName == undefined) {
            throw new Error("Sound group name is invalid.");
        }
        return this._soundGroups.get(soundGroupName);
    }

    HasSoundGroup(soundGroupName: string): boolean {
        if (soundGroupName == "" || soundGroupName == undefined) {
            throw new Error("Sound group name is invalid.");
        }
        return this._soundGroups.has(soundGroupName);
    }

    IsLoadingSound(serialId: number): boolean {
        return this._soundsBeingLoaded.includes(serialId);
    }

    PauseSound(serialId: number): void;
    PauseSound(serialId: number, fadeOutSeconds?: number): void;
    PauseSound(serialId: number, fadeOutSeconds?: number): void {
    }

    PlaySound(soundAssetName: string, soundGroupName: string): number;
    PlaySound(soundAssetName: string, soundGroupName: string, priority: number): number;
    PlaySound(soundAssetName: string, soundGroupName: string, playSoundParams: PlaySoundParams): number;
    PlaySound(soundAssetName: string, soundGroupName: string, userData?): number;
    PlaySound(soundAssetName: string, soundGroupName: string, priority: number, playSoundParams: PlaySoundParams): number;
    PlaySound(soundAssetName: string, soundGroupName: string, priority: number, userData?): number;
    PlaySound(soundAssetName: string, soundGroupName: string, playSoundParams: PlaySoundParams, userData?): number;
    PlaySound(soundAssetName: string, soundGroupName: string, priority: number, playSoundParams: PlaySoundParams, userData?): number;
    PlaySound(soundAssetName: string, soundGroupName: string, priority?: number | PlaySoundParams, playSoundParams?: PlaySoundParams, userData?): number {
        if (this._resourceManager == null)
        {
            throw new Error("You must set resource manager first.");
        }

        if (this._soundHelper == null)
        {
            throw new Error("You must set sound helper first.");
        }

        if (playSoundParams == null)
        {
            playSoundParams = PlaySoundParams.Create();
        }

        let serialId:number = ++this._serial;
        let errorCode:PlaySoundErrorCode = null;
        let errorMessage:string = null;
        let soundGroup:SoundGroup = this.GetSoundGroup(soundGroupName) as SoundGroup;
        if (soundGroup == null)
        {
            errorCode = PlaySoundErrorCode.SoundGroupNotExist;
            errorMessage = `Sound group '${soundGroupName}' is not exist.`;
        }
        else if (soundGroup.SoundAgentCount <= 0)
        {
            errorCode = PlaySoundErrorCode.SoundGroupHasNoAgent;
            errorMessage =`Sound group '${soundGroupName}' is have no sound agent.`;
        }

        if (errorCode != null)
        {
            if (this._playSoundFailureEventHandler != null)
            {
                let playSoundFailureEventArgs:PlaySoundFailureEventArgs = PlaySoundFailureEventArgs.Create(serialId, soundAssetName, soundGroupName, playSoundParams, errorCode, errorMessage, userData);
                this._playSoundFailureEventHandler(this, playSoundFailureEventArgs);
                ReferencePool.Release(playSoundFailureEventArgs);

                if (playSoundParams.referenced)
                {
                    ReferencePool.Release(playSoundParams);
                }

                return serialId;
            }
            throw new Error(errorMessage);
        }

        this._soundsBeingLoaded.push(serialId);
        //this._resourceManager.load(soundAssetName, priority, m_LoadAssetCallbacks, PlaySoundInfo.Create(serialId, soundGroup, playSoundParams, userData));
        return serialId;
    }

    get Priority(): number {
        return 940;
    }

    ResumeSound(serialId: number): void;
    ResumeSound(serialId: number, fadeInSeconds?: number): void;
    ResumeSound(serialId: number, fadeInSeconds?: number): void {
    }

    SetResourceManager(resourceManager: IResourceManager): void {
        if (resourceManager == null) {
            throw new Error("Resource manager is invalid.");
        }
        this._resourceManager = resourceManager;
    }

    SetSoundHelper(soundHelper: ISoundHelper): void {
        if (soundHelper == null) {
            throw new Error("Sound helper is invalid.");
        }

        this._soundHelper = soundHelper;
    }

    Shutdown(): void {
        this.StopAllLoadedSounds();
        this._soundGroups.clear();
        this._soundsBeingLoaded.length = 0;
        this._soundsToReleaseOnLoad.clear();
    }

    StopAllLoadedSounds(fadeOutSeconds?: number): void {
        this._soundGroups.forEach((value, key) => {
           value.StopAllLoadedSounds(fadeOutSeconds);
        })
    }

    StopAllLoadingSounds(): void {
        for (let i = 0; i < this._soundsBeingLoaded.length;i++){
            this._soundsToReleaseOnLoad.add(this._soundsBeingLoaded[i]);
        }
    }

    StopSound(serialId: number): boolean;
    StopSound(serialId: number, fadeOutSeconds?: number): boolean;
    StopSound(serialId: number, fadeOutSeconds?: number): boolean {
        if (this.IsLoadingSound(serialId))
        {
            this._soundsToReleaseOnLoad.add(serialId);
            for (let i = this._soundsBeingLoaded.length -1;i >= 0;i--){
                if(this._soundsBeingLoaded[i] == serialId){
                    this._soundsBeingLoaded.splice(i,1);
                }
            }
            return true;
        }

        this._soundGroups.forEach((value, key) => {
            if(value.StopSound(serialId,fadeOutSeconds)){
                return true;
            }
        })
        return false;
    }

    Update(elapseSeconds: number, realElapseSeconds: number): void {
    }

    get baseName(): string {
        return "GameFrameworkModule";
    }


}